use std::str::FromStr;

use actix_http::{Method, StatusCode};
use actix_web::{
    web::{self, Json, Path, ServiceConfig},
    HttpResponse,
};
use serde_json::Value;
use wither::bson::{doc, oid::ObjectId, Document};

use crate::{
    errors::cons_error,
    middlewares::authorize::{
        club_getters::{default_getter, path_getter},
        Authorize,
    },
    models::{
        club::ClubModelDTO,
        club_form::ClubFormModelDTO,
        invite::InviteTokenType,
        user::{NormalUserModel, UserModelDTO, UserRole},
    },
    services::{
        club_service::{ClubMemberDTO, ClubService, CreateClubReq, FindAllClubsReq, UpdateClubReq},
        form_service::FormService,
        invite_service::InviteService,
    },
    utils::{
        authorize::check_user_has_permission_with_object_id,
        db_utils::{build_filter, COMMON_NO_REGEXP},
        path_params::PathParamMDB,
    },
    AppError, AppResult, PaginationQuery,
};
mod ctrl_struct {
    use serde::{Deserialize, Serialize};
    #[derive(Debug, Clone, Serialize, Deserialize)]
    pub struct AddUserToClubReq {
        pub user_id: String,
    }
}
async fn find_all_clubs(
    club_service: web::Data<ClubService>,
    page: web::Query<PaginationQuery>,
) -> AppResult<HttpResponse> {
    let res = club_service
        .find_all_clubs(&FindAllClubsReq {
            filter: Value::Null,
            page: page.into_inner(),
        })
        .await?;

    Ok(HttpResponse::Ok().json(res.map_into::<ClubModelDTO>()))
}

async fn search_all_clubs(
    club_service: web::Data<ClubService>,
    page: web::Query<PaginationQuery>,
    search: web::Json<Value>,
) -> AppResult<HttpResponse> {
    let res = club_service
        .find_all_clubs(&FindAllClubsReq {
            filter: search.into_inner(),
            page: page.into_inner(),
        })
        .await?;
    Ok(HttpResponse::Ok().json(res.map_into::<ClubModelDTO>()))
}

async fn update_club(
    club_service: web::Data<ClubService>,
    club_id: Path<(String,)>,
    req: Json<UpdateClubReq>,
) -> AppResult<HttpResponse> {
    let oid = ObjectId::from_str(club_id.into_inner().0.as_str())?;
    Ok(HttpResponse::Ok().json(ClubModelDTO::from(
        club_service.update_club(oid, &req).await?,
    )))
}

async fn delete_club(
    club_service: web::Data<ClubService>,
    club_id: Path<(String,)>,
) -> AppResult<HttpResponse> {
    let oid = ObjectId::from_str(club_id.into_inner().0.as_str())?;
    club_service.delete_club(oid).await?;
    Ok(HttpResponse::NoContent().finish())
}

async fn create_club(
    club_service: web::Data<ClubService>,
    req: web::Json<CreateClubReq>,
) -> AppResult<HttpResponse> {
    let res = club_service.create_club(req.into_inner()).await?;
    Ok(HttpResponse::Ok().json(ClubModelDTO::from(res)))
}

async fn find_all_members(
    club_service: web::Data<ClubService>,
    query: web::Query<PaginationQuery>,
    path_params: PathParamMDB,
) -> AppResult<HttpResponse> {
    let club_id = get_path_param!(path_params, club_id, MSG_REQUIRE_CLUB_ID_IN_PATH);
    let res = club_service
        .find_all_member_in_club(club_id, Document::new(), &query)
        .await?;
    Ok(HttpResponse::Ok().json(res.map_into::<ClubMemberDTO>()))
}

async fn search_all_members(
    club_service: web::Data<ClubService>,
    where_value: web::Json<Option<Value>>,
    query: web::Query<PaginationQuery>,
    path_params: PathParamMDB,
) -> AppResult<HttpResponse> {
    let club_id = get_path_param!(path_params, club_id, MSG_REQUIRE_CLUB_ID_IN_PATH);
    let filter = build_filter(
        where_value.as_ref().unwrap_or(&Value::Null),
        &COMMON_NO_REGEXP,
    )
    .unwrap_or_default();
    let res = club_service
        .find_all_member_in_club(club_id, filter, &query)
        .await?;
    Ok(HttpResponse::Ok().json(res.map_into::<ClubMemberDTO>()))
}

/// ## join_club
/// 用户主动加入社团，前提是用户使用过invite_token并且此token的type为Immediate
async fn join_club(
    invite_service: web::Data<InviteService>,
    user: NormalUserModel,
    path_params: PathParamMDB,
) -> AppResult<HttpResponse> {
    let club_id = get_path_param!(path_params, club_id, MSG_REQUIRE_CLUB_ID_IN_PATH);
    let in_token = invite_service
        .check_user_in_club_invite_token(
            club_id,
            user.id,
            doc! {
                "type":InviteTokenType::Immediate
            },
        )
        .await?;
    if !in_token {
        return Err(AppError::Status(
            StatusCode::UNAUTHORIZED,
            cons_error::MSG_REQUIRE_AUTHORIZE.into(),
        ));
    }
    let res = invite_service
        .invite_user_to_club(user.clone(), user.id, club_id)
        .await?;

    Ok(HttpResponse::Ok().json(UserModelDTO::from(res)))
}

async fn add_to_club(
    invite_service: web::Data<InviteService>,
    user: NormalUserModel,
    path_params: PathParamMDB,
    req: Json<ctrl_struct::AddUserToClubReq>,
) -> AppResult<HttpResponse> {
    let club_id = get_path_param!(path_params, club_id, MSG_REQUIRE_CLUB_ID_IN_PATH);
    let user_id = ObjectId::from_str(&req.user_id)?;
    let res = invite_service
        .invite_user_to_club(user, user_id, club_id)
        .await?;
    Ok(HttpResponse::Ok().json(UserModelDTO::from(res)))
}

async fn find_form_in_club(
    form_service: web::Data<FormService>,
    query: web::Query<PaginationQuery>,
    path_params: PathParamMDB,
    user: NormalUserModel,
) -> AppResult<HttpResponse> {
    let club_id = get_path_param!(path_params, club_id, MSG_REQUIRE_CLUB_ID_IN_PATH);
    let where_value = doc! {"club_id":&club_id};
    let res = {
        if check_user_has_permission_with_object_id(user, club_id, UserRole::LeaderOrAdmin) {
            form_service.find_all_forms(&query, where_value).await?
        } else {
            form_service
                .find_all_forms_outer(&query, where_value)
                .await?
        }
    };
    Ok(HttpResponse::Ok().json(res.map_into::<ClubFormModelDTO>()))
}

async fn search_form_in_club(
    form_service: web::Data<FormService>,
    query: web::Query<PaginationQuery>,
    where_value: web::Json<Option<Value>>,
    path_params: PathParamMDB,
    user: NormalUserModel,
) -> AppResult<HttpResponse> {
    let club_id = get_path_param!(path_params, club_id, MSG_REQUIRE_CLUB_ID_IN_PATH);
    let mut filter = build_filter(
        where_value.as_ref().unwrap_or(&Value::Null),
        &COMMON_NO_REGEXP,
    )
    .unwrap_or_default();
    filter.insert("club_id", &club_id);
    let res = {
        if check_user_has_permission_with_object_id(user, club_id, UserRole::LeaderOrAdmin) {
            form_service.find_all_forms(&query, filter).await?
        } else {
            form_service.find_all_forms_outer(&query, filter).await?
        }
    };
    Ok(HttpResponse::Ok().json(res.map_into::<ClubFormModelDTO>()))
}

pub fn configure_club_controller(config: &mut ServiceConfig) {
    config
        .service(
            web::resource("")
                .route(web::get().to(find_all_clubs))
                .route(web::post().to(create_club))
                .wrap(Authorize::new(UserRole::Admin, default_getter).method(Method::POST)),
        )
        .service(
            web::scope("/single/{club_id}")
                .service(
                    web::resource("")
                        .route(web::put().to(update_club))
                        .route(web::delete().to(delete_club))
                        .wrap(
                            Authorize::new(UserRole::LeaderOrAdmin, path_getter("club_id"))
                                .method(Method::PUT)
                                .method(Method::DELETE),
                        ),
                )
                .service(web::resource("/members").route(web::get().to(find_all_members)))
                .service(web::resource("/members/search").route(web::post().to(search_all_members)))
                .service(web::resource("/join").route(web::post().to(join_club)))
                .service(
                    web::resource("/add")
                        .route(web::post().to(add_to_club))
                        .wrap(Authorize::new(UserRole::Admin, default_getter)),
                )
                .service(web::resource("/forms").route(web::get().to(find_form_in_club)))
                .service(web::resource("/forms/search").route(web::post().to(search_form_in_club))),
        )
        .service(web::resource("/search").route(web::post().to(search_all_clubs)));
}
